package com.example.dakotanews.service.implement;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.example.dakotanews.dto.*;
import com.example.dakotanews.dto.utils.ApiResponse;
import com.example.dakotanews.dto.utils.ResponseCode;
import com.example.dakotanews.entity.*;
import com.example.dakotanews.entity.comment.NewsComment;
import com.example.dakotanews.entity.relation.NewsIssue;
import com.example.dakotanews.entity.relation.NewsMark;
import com.example.dakotanews.entity.relation.UserFollowing;
import com.example.dakotanews.entity.User;
import com.example.dakotanews.mapper.*;
import com.example.dakotanews.mapper.comment.NewsCommentMapper;
import com.example.dakotanews.mapper.relation.NewsIssueMapper;
import com.example.dakotanews.mapper.relation.NewsMarkMapper;
import com.example.dakotanews.mapper.relation.UserFollowingMapper;
import com.example.dakotanews.service.NewsService;
import com.example.dakotanews.utils.TimeHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class NewsServiceImpl implements NewsService {

    private final NewsMapper newsMapper;
    private final UserMapper userMapper;
    private final NewsCommentMapper newsCommentMapper;
    private final UserFollowingMapper userFollowingMapper;
    private final NewsIssueMapper newsIssueMapper;
    private final IssueMapper issueMapper;
    private final BlogMapper blogMapper;
    private final NewsMarkMapper newsMarkMapper;
    private final NewsSortMapper newsSortMapper;
    private final TimeHandler timeHandler;

    // 使用构造函数进行自动装配
    @Autowired
    public NewsServiceImpl(
            NewsMapper newsMapper,
            UserMapper userMapper,
            NewsCommentMapper newsCommentMapper,
            UserFollowingMapper userFollowingMapper,
            NewsIssueMapper newsIssueMapper,
            IssueMapper issueMapper,
            BlogMapper blogMapper,
            NewsMarkMapper newsMarkMapper,
            NewsSortMapper newsSortMapper,
            TimeHandler timeHandler
    ) {
        this.newsMapper = newsMapper;
        this.userMapper = userMapper;
        this.newsCommentMapper = newsCommentMapper;
        this.userFollowingMapper = userFollowingMapper;
        this.newsIssueMapper = newsIssueMapper;
        this.issueMapper = issueMapper;
        this.blogMapper = blogMapper;
        this.newsMarkMapper = newsMarkMapper;
        this.newsSortMapper = newsSortMapper;
        this.timeHandler = timeHandler;
    }

    ApiResponse<Object> apiResponse;

    /**
     * @description 需要实现的业务（数据结构定义见接口文档）：
     * 1、根据新闻ID查询新闻记录，获取需要返回的字段
     * 2、根据新闻ID查询新闻话题关系表，返回一个List集合
     * 3、根据查询到的新闻记录中的userID查询用户记录，获取需要返回的字段
     * 4、修改新闻记录和新闻标记记录（若已登录状态访问），二者的浏览量字段自增
     */
    @Override
    public ApiResponse<Object> getNewsAllInfo(Integer newsId, Integer userId) {
        // 根据参数新闻ID查询新闻记录
        News news = newsMapper.selectNewsById(newsId);

        // 以下集合类型均用于让返回数据呈现为标准结构
        Map<String, Object> newsBody = new HashMap<>();
        Map<String, Object> data = new HashMap<>();

        // 如果获取到了新闻
        if (news != null) {
            NewsDetailDTO newsDetailDTO = new NewsDetailDTO(news);
            newsDetailDTO.setPublishTime(timeHandler.dateTimeFormat(news.getPublishTime()));
            if (newsDetailDTO.getEditTime() != null) {
                newsDetailDTO.setEditTime(timeHandler.dateTimeFormat(news.getEditTime()));
            }

            data.put("issue", getNewsIssues(newsId));

            Integer authorId = news.getUserId();
            // 统计作者的文章数和被点赞数
            int newsNum = Math.toIntExact(newsMapper.selectCount(new QueryWrapper<News>()
                    .eq("user_id", authorId)));
            // 分别统计新闻的被点赞数和博客的被点赞数
            int newsSupportedNum = newsMapper.selectList(new QueryWrapper<News>()
                            .eq("user_id", authorId))
                    .stream()
                    .mapToInt(News::getSupportNum)
                    .sum();
            int blogSupportedNum = blogMapper.selectList(new QueryWrapper<Blog>()
                            .eq("user_id", authorId))
                    .stream()
                    .mapToInt(Blog::getSupportNum)
                    .sum();
            int supportedNum = newsSupportedNum + blogSupportedNum;

            boolean followedIf;
            if (userId != null) {
                // 若用户已登录，并且作者用户（被关注）与登录用户（关注）的关系存在
                UserFollowing userFollowing = userFollowingMapper.selectOne(new QueryWrapper<UserFollowing>()
                        .eq("followed_user_id", authorId)
                        .eq("user_id", userId));

                followedIf = userFollowing != null;
            } else {
                followedIf = false;
            }

            // 获取新闻作者信息
            User authorUser = userMapper.selectById(authorId);
            AuthorDTO authorDTO = new AuthorDTO(authorId,
                    authorUser.getNickname(),
                    authorUser.getProfile(),
                    authorUser.getIntroduction(),
                    newsNum,
                    authorUser.getFollowerNum(),
                    supportedNum,
                    followedIf);

            // 调用接口时，让对应新闻的浏览量加一
            newsMapper.update(new UpdateWrapper<News>()
                    .eq("id", newsId)
                    .setSql("page_view = page_view + 1"));

            // 若访客用户为已登录状态，获取其对当前新闻的操作标识
            if (userId != null) {
                Map<String, Object> operation = new HashMap<>();

                // 查询 新闻标记记录 是否存在
                NewsMark newsMark = newsMarkMapper.selectOne(new QueryWrapper<NewsMark>()
                        .eq("news_id", news.getId())
                        .eq("user_id", userId));
                if (newsMark == null) {
                    // 若没有对应记录，则新建记录
                    NewsMark newsMarkNew = new NewsMark();
                    newsMarkNew.setNewsId(newsId);
                    newsMarkNew.setUserId(userId);
                    newsMarkMapper.insert(newsMarkNew);

                    operation.put("newsSupportIf", 0);
                    operation.put("newsOppositionIf", 0);
                    operation.put("newsCollectionIf", 0);

                } else {
                    // 若有对应记录，则修改记录的浏览量字段加一
                    newsMarkMapper.update(new UpdateWrapper<NewsMark>()
                            .eq("id", newsMark.getId())
                            .setSql("page_view = page_view + 1"));

                    operation.put("newsSupportIf", newsMark.getSupportIf());
                    operation.put("newsOppositionIf", newsMark.getOppositionIf());
                    operation.put("newsCollectionIf", newsMark.getCollectionIf());
                }

                data.put("operation", operation);
            }

            // 封装新闻详情和作者信息
            newsBody.put("newsInfo", newsDetailDTO);
            newsBody.put("authorInfo", authorDTO);

            data.put("newsBody", newsBody);

            apiResponse = new ApiResponse<>(ResponseCode.SUCCESS, "获取新闻详情页数据成功", data);
        } else {
            apiResponse = new ApiResponse<>(ResponseCode.NOT_FOUND, "新闻不存在", null);
        }
        return apiResponse;
    }

    @Override
    public ApiResponse<Object> supportNews(Integer newsId, Integer userId) {

        // 查询是否为已点赞状态
        int supportIf = newsMarkMapper.selectOne(new QueryWrapper<NewsMark>()
                .eq("news_id", newsId)
                .eq("user_id", userId)).getSupportIf();

        UpdateWrapper<NewsMark> newsMarkUpdateWrapper = new UpdateWrapper<NewsMark>()
                .eq("news_id", newsId)
                .eq("user_id", userId);

        if (supportIf == 0) {
            // 若未点赞
            newsMarkUpdateWrapper.setSql("support_if = 1, opposition_if = 0");
            // 别忘了执行更新方法
            newsMarkMapper.update(null, newsMarkUpdateWrapper);
            apiResponse = new ApiResponse<>(ResponseCode.SUCCESS, "请求成功", "点赞成功");
        } else {
            // 若已点赞
            newsMarkUpdateWrapper.set("support_if", 0);
            newsMarkMapper.update(null, newsMarkUpdateWrapper);
            apiResponse = new ApiResponse<>(ResponseCode.SUCCESS, "请求成功", "取消点赞");
        }
        return apiResponse;
    }

    @Override
    public ApiResponse<Object> oppositeNews(Integer newsId, Integer userId) {
        // 查询是否为已点踩状态
        int oppositionIf = newsMarkMapper.selectOne(new QueryWrapper<NewsMark>()
                .eq("news_id", newsId)
                .eq("user_id", userId)).getOppositionIf();

        UpdateWrapper<NewsMark> newsMarkUpdateWrapper = new UpdateWrapper<NewsMark>()
                .eq("news_id", newsId)
                .eq("user_id", userId);

        if (oppositionIf == 0) {
            // 若未点踩
            newsMarkUpdateWrapper.setSql("opposition_if = 1, support_if = 0");
            newsMarkMapper.update(null, newsMarkUpdateWrapper);
            apiResponse = new ApiResponse<>(ResponseCode.SUCCESS, "请求成功", "点踩成功");
        } else {
            // 若已点踩
            newsMarkUpdateWrapper.set("opposition_if", 0);
            newsMarkMapper.update(null, newsMarkUpdateWrapper);
            apiResponse = new ApiResponse<>(ResponseCode.SUCCESS, "请求成功", "取消点踩");
        }
        return apiResponse;
    }

    @Override
    public ApiResponse<Object> collectNews(Integer newsId, Integer userId) {
        // 查询是否为已收藏状态
        int collectionIf = newsMarkMapper.selectOne(new QueryWrapper<NewsMark>()
                .eq("news_id", newsId)
                .eq("user_id", userId)).getCollectionIf();

        UpdateWrapper<NewsMark> newsMarkUpdateWrapper = new UpdateWrapper<NewsMark>()
                .eq("news_id", newsId)
                .eq("user_id", userId);

        if (collectionIf == 0) {
            // 若未收藏
            newsMarkUpdateWrapper.set("collection_if", 1);
            newsMarkMapper.update(null, newsMarkUpdateWrapper);
            apiResponse = new ApiResponse<>(ResponseCode.SUCCESS, "请求成功", "收藏成功");
        } else {
            // 若已收藏
            newsMarkUpdateWrapper.set("collection_if", 0);
            newsMarkMapper.update(null, newsMarkUpdateWrapper);
            apiResponse = new ApiResponse<>(ResponseCode.SUCCESS, "请求成功", "取消收藏");
        }
        return apiResponse;
    }

    @Override
    public ApiResponse<Object> getNewsSort() {

        List<NewsSort> newsSortList = newsSortMapper.selectList(new QueryWrapper<>());
        Map<String, Object> data = new HashMap<>();
        if (newsSortList.size() == 0) {
            // 如果没有新闻分类记录（这在理论上是不存在的，但是为了代码健壮性还是加上了这个判断）
            return new ApiResponse<>(ResponseCode.NOT_FOUND, "没有新闻分类", null);
        }
        List<NewsSortDTO> newsSortDTOList = new ArrayList<>();
        for (NewsSort newsSort : newsSortList) {
            NewsSortDTO newsSortDTO = new NewsSortDTO(newsSort.getId(), newsSort.getSortName());
            newsSortDTOList.add(newsSortDTO);
        }
        // 将数据进行封装
        data.put("newsSorts", newsSortDTOList);

        return new ApiResponse<>(ResponseCode.SUCCESS, "请求成功", data);
    }

    /**
     * @param newsSort 新闻分类，根据分类表查询对应分类。0 代表不分类
     * @description 根据分类获取新闻推荐的一般推荐方法。解释一下业务逻辑：
     * 1、根据分类，查询新闻表
     * 2、对每条新闻记录，获取点赞数、点踩数、收藏数、话题数、浏览量和发布时间，这些可以直接获取的，此外还需要获取评论数
     * 3、再根据当前新闻ID查询新闻评论表，获取记录的数量作为评论数
     * 4、令以上字段的值和对应的权重（）相乘后再相加，最终得到的值就是推荐指数。再将DTO需要的新闻信息和推荐指数赋值给DTO类
     * 5、这样就可以得到一个DTO的List集合。再根据其中的推荐指数对集合进行降序排序。
     * 6、再根据page和pageSize对集合进行分页处理，将结果封装到响应体的Data中返回
     */
    @Override
    public ApiResponse<Object> commonRecommend(Integer newsSort, int page, int pageSize) {

        QueryWrapper<News> newsQueryWrapper = new QueryWrapper<>();
        if (newsSort != 0) {
            // 若指定了分类则增加条件构造器
            newsQueryWrapper.eq("sort_id", newsSort);
        }
        Map<String, Object> data = new HashMap<>();
        List<News> newsList = newsMapper.selectList(newsQueryWrapper);
        if (newsList.size() == 0) {
            // 如果分类没有对应新闻
            apiResponse = new ApiResponse<>(ResponseCode.NOT_FOUND, "没有对应新闻", null);
            return apiResponse;
        }
        List<PlainNewsDTO> newsDTOList = new ArrayList<>();

        // 对每条 新闻记录 进行操作
        for (News news : newsList) {
            // 定义每个参数的权重
            int supWeight = 15;
            int oppWeight = 5;
            int colWeight = 20;
            int issWeight = 15;
            int pagWeight = 1;
            int comWeight = 10;

            // 为每个参数赋值
            int supNum = news.getSupportNum();
            int oppNum = news.getOppositionNum();
            int colNum = news.getCollectionNum();
            int issNum = news.getIssueNum();
            int pagNum = news.getPageView();
            // 获取新闻发布时到现在的时间间隔（天数）
            int pastDays = (int) Duration.between(news.getPublishTime(), LocalDateTime.now()).toDays();
            // 计算时间指数，发布时间越新，该指数越大
            int timeIndex;
            if (pastDays < 3) {
                timeIndex = (3 - pastDays) * 10;
            } else if (pastDays < 7) {
                timeIndex = 5;
            } else if (pastDays < 30) {
                timeIndex = 2;
            } else {
                timeIndex = 1;
            }

            int comNum = Math.toIntExact(newsCommentMapper.selectCount(new QueryWrapper<NewsComment>()
                    .eq("news_id", news.getId())));

            // 计算推荐指数。时间指数比较特殊，相当于最终值的权重。
            int recommendIndex = (supNum * supWeight + oppNum * oppWeight + colNum * colWeight +
                    issNum * issWeight + pagNum * pagWeight + comNum * comWeight) * timeIndex;
            // System.out.println(recommendIndex);

            String author = userMapper.selectById(news.getUserId()).getNickname();
            // 查询对应的所有话题ID，返回话题ID的List集合
            List<Integer> issueIds = newsIssueMapper.selectList(new QueryWrapper<NewsIssue>()
                            .eq("news_id", news.getId()))
                    .stream()
                    .map(NewsIssue::getIssueId)
                    .toList();
            List<String> issues;
            // 若新闻带有话题
            if (issueIds.size() != 0) {
                // 根据话题ID的List集合查询每一条话题名，返回话题名的List集合
                issues = issueMapper.selectList(new QueryWrapper<Issue>()
                                .in("id", issueIds))
                        .stream()
                        .map(Issue::getIssueName)
                        .toList();
            } else {
                issues = null;
            }
            // DTO的数据封装
            PlainNewsDTO newsDTO = new PlainNewsDTO(
                    news.getId(),
                    news.getTitle(),
                    news.getSummary(),
                    author, news.getSupportNum(),
                    news.getCollectionNum(),
                    timeHandler.dateTimeFormat(news.getPublishTime()),
                    news.getCover(),
                    issues,
                    recommendIndex);
            newsDTOList.add(newsDTO);
        }

        // 对集合进行排序，按照推荐指数降序排序
        List<PlainNewsDTO> plainNewsDTOList = newsDTOList.stream()
                .sorted((n1, n2) -> Integer.compare(n2.getRecommendIndex(), n1.getRecommendIndex())).toList();

        // 获取所有新闻记录的数目（与分页无关）
        int total = plainNewsDTOList.size();
        data.put("total", total);

        if (pageSize != 0) {
            // 若分页大小不为0（说明需要分页）
            // 计算起始索引
            int fromIndex = (page - 1) * pageSize;
            if (fromIndex >= plainNewsDTOList.size()) {
                data.put("plainNewsInfoList", null);
                apiResponse = new ApiResponse<>(ResponseCode.NOT_FOUND, "没有更多新闻了", data);
            } else {
                int toIndex = Math.min(fromIndex + pageSize, plainNewsDTOList.size());
                data.put("plainNewsInfoList", plainNewsDTOList.subList(fromIndex, toIndex));
                apiResponse = new ApiResponse<>(ResponseCode.SUCCESS, "分页查询请求成功", data);
            }
        } else {
            // 若不分页直接返回
            data.put("plainNewsInfoList", plainNewsDTOList);
            apiResponse = new ApiResponse<>(ResponseCode.SUCCESS, "请求成功", data);
        }

        return apiResponse;
    }

    @Override
    public ApiResponse<Object> personalizedRecommend(Integer newsSort, Integer userId, int page, int pageSize) {

        return apiResponse;
    }

    /**
     * @param newsId （必选）待编辑的新闻ID
     * @param userId （必选）操作用户ID
     * @description
     * 1、先检查待编辑的新闻的作者ID与操作用户ID是否相同，若不相同直接返回 403 错误
     * 2、若验证通过，则获取新闻的指定信息
     */
    @Override
    public ApiResponse<Object> getNewsEditForm(Integer newsId, Integer userId) {
        Map<String, Object> data = new HashMap<>();

        News editNews = newsMapper.selectById(newsId);
        if (editNews == null) {
            // 如果没查询到新闻ID对应记录
            return new ApiResponse<>(ResponseCode.NOT_FOUND, "新闻不存在", null);
        }

        Integer authorId = newsMapper.selectById(newsId).getUserId();
        if (!authorId.equals(userId)) {
            // 若新闻作者ID与JWT解析出的用户ID不匹配
            return new ApiResponse<>(ResponseCode.FORBIDDEN, "请求被拒绝", null);
        }
        NewsEditFormDTO newsEditFormDTO = new NewsEditFormDTO(editNews);

        List<String> issues = getNewsIssues(newsId);
        newsEditFormDTO.setIssues(issues);

        data.put("form", newsEditFormDTO);

        return new ApiResponse<>(ResponseCode.SUCCESS, "请求成功", data);
    }

    /**
     * @description 内部方法。根据新闻ID查询对应的话题
     * @return 若有话题，返回话题列表；否则返回空
     */
    protected List<String> getNewsIssues(int newsId) {
        // 查询对应的所有话题ID，返回话题ID的List集合
        List<Integer> issueIds = newsIssueMapper.selectList(new QueryWrapper<NewsIssue>()
                        .eq("news_id", newsId))
                .stream()
                .map(NewsIssue::getIssueId)
                .toList();
        // 若新闻带有话题
        if (issueIds.size() != 0) {
            // 根据话题ID的List集合查询每一条话题名，返回话题名的List集合
            return issueMapper.selectList(new QueryWrapper<Issue>()
                            .in("id", issueIds))
                    .stream()
                    .map(Issue::getIssueName)
                    .toList();
        } else {
            return new ArrayList<>();
        }
    }

    // 默认分类名是存在的，否则会报错500
    @Override
    public ApiResponse<Object> addNews(NewsEditFormDTO newsEditFormDTO, Integer userId) {
        // 新建新闻类并插入记录
        News news = new News();
        news.setUserId(userId);
        int issueNum = setNews(newsEditFormDTO, news);

        newsMapper.insert(news);
        Integer newsId = news.getId();

        // 新增新闻话题关系记录
        Integer newNewsId = setIssues(newsEditFormDTO, issueNum, newsId);

        return new ApiResponse<>(ResponseCode.SUCCESS, "新闻发布成功", newNewsId);
    }

    // 默认新闻ID存在，否则报错500
    @Override
    public ApiResponse<Object> editNews(NewsEditFormDTO newsEditFormDTO, Integer newsId) {
        // 定义afterNews代表修改后的新闻实体类
        News afterNews = newsMapper.selectById(newsId);
        int issueNum = setNews(newsEditFormDTO, afterNews);

        // 对newsId对应的新闻记录进行修改
        newsMapper.update(afterNews, new QueryWrapper<News>()
                .eq("id", newsId));

        // 修改话题关系表
        // 先删除对应新闻的全部记录
        newsIssueMapper.delete(new QueryWrapper<NewsIssue>()
                .eq("news_id", newsId));
        // 新增新闻话题关系记录
        setIssues(newsEditFormDTO, issueNum, newsId);

        return new ApiResponse<>(ResponseCode.SUCCESS, "新闻修改成功", newsId);
    }

    // 根据修改信息表单DTO创建新闻实体类，然后返回话题数
    private int setNews(NewsEditFormDTO newsEditFormDTO, News news) {
        news.setSortId(newsEditFormDTO.getSortId());
        news.setTitle(newsEditFormDTO.getTitle());
        news.setCover(newsEditFormDTO.getCover());
        news.setSummary(newsEditFormDTO.getSummary());
        news.setContent(newsEditFormDTO.getContent());
        int issueNum = (newsEditFormDTO.getIssues()).size();
        news.setIssueNum(issueNum);

        return issueNum;
    }

    // 依据话题对应的id，创建新的话题列表并返回包含被新增或者修改的新闻id的请求体    默认话题名是存在的，否则会报错500
    private Integer setIssues(NewsEditFormDTO newsEditFormDTO, int issueNum, Integer newsId) {
        if(issueNum > 0) {
            for(String issue: newsEditFormDTO.getIssues()) {
                Integer issueId = issueMapper.selectOne(new QueryWrapper<Issue>()
                        .eq("issue_name", issue)).getId();
                NewsIssue newsIssue = new NewsIssue();
                newsIssue.setIssueId(issueId);
                newsIssue.setNewsId(newsId);
                newsIssueMapper.insert(newsIssue);
            }
        }

        return newsId;
    }
}
